有時你可能會發現自己考慮將一個或多個done通道合併到一個done通道中,該通道在任何組件通道關閉時關閉。編寫一個執行這種耦合度較高的select語句是可行的,盡管很冗長;
但是有時你無法知道運行狀態下done通道的數量。在這種情況下,或者你如果喜歡單線操作,你可以使用or通道模式將這些通道組合在一起。
這種模式使用遞歸和goroutine創建一個複合done通道。
下面這個範例程式碼實現了一個名為 or 的函數,這個函數的目的是等待多個 channel 中的任何一個發送數據(或者關閉),並且在其中一個 channel 發送數據時,發送數據到一個新的 channel。這種模式通常用於合並或者選擇多個不同的 channel 中的數據。
func main() {
// 定義一個 or 函數
var or func(channels ...<-chan interface{}) <-chan interface{}
or = func(channels ...<-chan interface{}) <-chan interface{} {
// 如果沒有 channels,返回 nil
switch len(channels) {
case 0: // 沒有傳入的 channel
return nil
case 1: // 只有一個傳入的 channel
return channels[0]
}
// 創建一個新的 channel 用於返回
orDone := make(chan interface{})
go func() {
defer close(orDone)
switch len(channels) {
case 2: // 只有兩個傳入的 channel
select {
case <-channels[0]:
case <-channels[1]:
}
default: // 有超過兩個的 channels
select {
case <-channels[0]:
case <-channels[1]:
case <-channels[2]:
// 遞歸調用 or 函數,略過前三個 channels 並添加 orDone
// 以便在內部 goroutine 完成時能通知外部
case <-or(append(channels[3:], orDone)...):
}
}
}()
return orDone
}
// 創建一個函數來模擬訊號,該函數在指定時間後發送一個消息
sig := func(after time.Duration) <-chan interface{} {
c := make(chan interface{})
go func() {
defer close(c)
time.Sleep(after)
}()
return c
}
// 測試 or 函數
start := time.Now()
<-or(
sig(2*time.Hour),
sig(5*time.Minute),
sig(1*time.Second),
sig(1*time.Hour),
sig(1*time.Minute),
)
fmt.Printf("done after %v", time.Since(start))
}
詳細的程式說明:
1.or 函數是一個變數函數,它接受一個或多個只讀 channel (<-chan interface{}),並返回一個只讀 channel。
2.or 函數的內部邏輯是:
如果沒有傳入任何 channel,返回 nil。
如果只有一個 channel,直接返回這個 channel。
如果有兩個或更多的 channel,則創建一個新的 channel orDone,並啟動一個 goroutine 來等待任一傳入 channel 的數據。然後,該 goroutine 會根據傳入 channel 的數量執行不同的 select 語句來接收數據,並在 orDone 上發送數據。
3.sig 函數是一個輔助函數,它模擬發送信號的行為。sig 會創建一個 channel,並在給定的時間 after 之後關閉這個 channel,用於模擬異步事件的觸發。
4.在 main 函數中,使用 sig 函數創建了五個不同的 channel,每個 channel 都會在不同的時間後關閉。
5.or 函數被呼叫,並且這五個 channel 被傳入,or 函數返回一個新的 channel。
6.main 函數通過 <-or(...) 在新的 channel 上等待。當五個輸入 channel 中的任何一個關閉時,or 函數會收到通知,並立即關閉 orDone channel,從而結束 main 函數的等待。
7.main 函數打印出從開始等待到結束等待的總時間。